class Texter {
    constructor(ctx){
      this.ctx = ctx
      this.ref_size = 2000
      this.padding_left = 0
      this.padding_right = 0
      this.c = 0
      this.padding_bottom = 0
      this.line_space = 0
      this.letter_space = 0
    }
    toGeo(v){
      return 100 * v / this.ref_size
    }
    toPixel(v){
      return Math.ceil(v * this.ref_size / 100)
    }
    calculate(text){
      var debug = true
      var x = this.padding_left
      var y = this.padding_top
      var ls = []
      var total_w = 0
      var total_h = 0
      var row_w = 0
      var row_h = 0
      var row_count = 0
      var col_count = 0
      for(var i = 0; i < text.length; ++i){
        var l = text[i]
        if(l == '\r')
            continue
        var {
          actualBoundingBoxAscent: aA,
          actualBoundingBoxDescent: aD,
          actualBoundingBoxLeft: aL,
          actualBoundingBoxRight: aR,
          fontBoundingBoxAscent: fA, 
          fontBoundingBoxDescent: fD,
          width: w
        } = this.ctx.measureText(l !== '\t' ? l : '    ')
        var h = fA + fD;
        row_h = Math.max(row_h, h)
        
        ++col_count
        if(l == ' ' || l == '\n' || l == '\t'){
          aA = fA
          aD = fD
          aL = 0
          aR = w
        }
        if(debug){
          ls.push({
            l,
            x: this.toGeo(x),
            y: this.toGeo(y),
            w: this.toGeo(w),
            fD: this.toGeo(fD),
            fA: this.toGeo(fA),
            aA: this.toGeo(aA),
            aD: this.toGeo(aD),
            aL: this.toGeo(aL),
            aR: this.toGeo(aR)
          })
        } else {
          ls.push({
            l,
            aA: this.toGeo(aA - y - fA),
            aD: this.toGeo(aD + y + fA),
            aL: this.toGeo(aL - x),
            aR: this.toGeo(aR + x),
          })
        }
        x += w + this.letter_space
        row_w += w + this.letter_space
        if(l === '\n') {
          ++row_count
          col_count = 0
          total_h += row_h + this.line_space
          if(col_count > 0)
            row_w -= this.letter_space
          total_w = Math.max(row_w - this.letter_space, total_w)
          x = this.padding_left
          y += row_h + this.line_space
          row_w = 0
          continue
        }
      }
      total_h += this.padding_top + this.padding_bottom + row_h
      if(row_count > 0)
        total_h -= this.line_space
      total_w = Math.max(row_w - this.letter_space, total_w)
      total_w += this.padding_left + this.padding_right
      var {
        fontBoundingBoxAscent: fA, 
        fontBoundingBoxDescent: fD
      } = this.ctx.measureText('a')
      return {
        debug,
        padding_left: this.toGeo(this.padding_left), 
        padding_right: this.toGeo(this.padding_right), 
        padding_top: this.toGeo(this.padding_top), 
        padding_bottom: this.toGeo(this.padding_bottom), 
        line_space: this.toGeo(this.line_space), 
        letter_space: this.toGeo(this.letter_space), 
        total_w: this.toGeo(total_w), 
        total_h: this.toGeo(total_h),
        fA: this.toGeo(fA), 
        fD: this.toGeo(fD),
        ls
      }
    }
    drawText(info){
      var { fA, fD, ls, selectionStart, selectionEnd, padding_top, line_space } = info
      var gfA = this.toPixel(fA)
      var gfD = this.toPixel(fD)
      var padding_top = this.toPixel(padding_top)
      var line_space = this.toPixel(line_space)
      var line_height = line_space + gfA + gfD
      var selection = [selectionStart, selectionEnd]
      var selection_rects = []
      var new_row = false
      var row = 0
      var ls_len = ls.length
      for(var i = 0; i < ls_len; ++i){
        var data = ls[i]
        var { l, aA, aD, aL, aR } = data
        var { x, y, w, fA, fD } = data // debug only.
        x = this.toPixel(x)   || 0
        y = this.toPixel(y)   || 0
        w = this.toPixel(w)   || 0
        fA = this.toPixel(fA)  || 0
        fD = this.toPixel(fD)  || 0
        aA = this.toPixel(aA)
        aD = this.toPixel(aD)
        aL = this.toPixel(aL)
        aR = this.toPixel(aR)
        var { 
            actualBoundingBoxAscent: raA,
            actualBoundingBoxDescent: raD,
            actualBoundingBoxLeft: raL,
            actualBoundingBoxRight: raR,
        } = ctx_b.measureText(l)
        var real_h = raA + raD
        var real_w = raR + raL
        var ref_h = aD + aA
        var ref_w = aR + aL
        var baselineY = 0
        var baselineX = 0
        var scaleW = ref_w / real_w
        var scaleH = ref_h / real_h
        baselineX = (x - aL) / scaleW + raL 
        baselineY = (y + fA + aD) / scaleH - raD
        if(i >= selection[0] && i < selection[1]){
          var sx = (x - aL)
          var sw = ref_w
          if(i == selection[0] || new_row){
            var sy = padding_top + row * line_height
            var sh = gfA + gfD
            selection_rects.push(sx)
            selection_rects.push(sy)
            selection_rects.push(sw)
            selection_rects.push(sh)
            new_row = false
          } else {
            var len = selection_rects.length
            selection_rects[len - 2] = (sx + sw) - selection_rects[len - 4]
          }
        }
        if(l == '\n'){
          ++row
          new_row = true
        }
        this.ctx.save()
        this.ctx.scale(scaleW, scaleH)
        this.ctx.fillText(l, baselineX, baselineY)
        this.ctx.restore()
      }
      var len = selection_rects.length
      var temp = this.ctx.globalCompositeOperation
      this.ctx.fillStyle = '#0088ff'
      this.ctx.globalCompositeOperation = 'hue'
      for(var i = 0; i < len; i += 4){
        var x = selection_rects[i]
        var y = selection_rects[i+1]
        var w = selection_rects[i+2]
        var h = selection_rects[i+3]
        this.ctx.fillRect(x,y,w,h)
      }
      this.ctx.globalCompositeOperation = temp  
    }
    drawDebug(info){
      var sp = (l)=>{
        switch(l){
          case '\n': return '\\n'
          case '\t': return '\\t'
          case ' ':  return '\\s'
          default:   return ''
        }
      }
      var { debug, ls } = info
      var ls_len = ls.length
      for(var i = 0; i < ls_len; ++i){
        var data = ls[i]
        var { l, x, y, w, fA, fD, aA, aD, aL, aR } = data
        
        aA = this.toPixel(data.aA)
        aD = this.toPixel(data.aD)
        aL = this.toPixel(data.aL)
        aR = this.toPixel(data.aR)

        x = this.toPixel(data.x)  
        y = this.toPixel(data.y)  
        w = this.toPixel(data.w)  
        fA = this.toPixel(data.fA)
        fD = this.toPixel(data.fD)

        this.ctx.strokeStyle = '#999999'
        this.ctx.lineWidth = Math.ceil(100 / edt_scale.value)
        this.ctx.setLineDash([this.ctx.lineWidth*4, this.ctx.lineWidth])
        
        if(!debug){
          if(aR + aL > 0 && aD + aA > 0)
            this.ctx.strokeRect(-aL, -aA, aR + aL,  aD + aA)
          // sp letter
          this.ctx.font = 'normal 10px Sans Serif'
          this.ctx.fillStyle = 'black'
          this.ctx.fillText(sp(l), -aL, -aA)
          continue
        }

        // font bounding box.
        this.ctx.strokeRect(x, y, w, fD + fA)
        
        // baseline
        this.ctx.strokeStyle='#990099'
        this.ctx.beginPath();
        this.ctx.moveTo(x, y + fA);
        this.ctx.lineTo(x + w, y + fA);
        this.ctx.stroke();
        
        // ascent
        this.ctx.strokeStyle='#009999'
        this.ctx.beginPath();
        this.ctx.moveTo(x, y + fA - aA);
        this.ctx.lineTo(x + w, y + fA - aA);
        this.ctx.stroke();
    
        // descent
        this.ctx.strokeStyle='#999900'
        this.ctx.beginPath();
        this.ctx.moveTo(x, y + fA + aD);
        this.ctx.lineTo(x + w, y + fA + aD);
        this.ctx.stroke();
    
        // left
        this.ctx.strokeStyle='#902950'
        this.ctx.beginPath();
        this.ctx.moveTo(x - aL, y);
        this.ctx.lineTo(x - aL, y + fA + fD);
        this.ctx.stroke();
    
        // right
        this.ctx.strokeStyle='#902950'
        this.ctx.beginPath();
        this.ctx.moveTo(x + aR, y);
        this.ctx.lineTo(x + aR, y + fA + fD);
        this.ctx.stroke();

        // sp letter
        this.ctx.font = 'normal '+Math.min(w, fA + fD)+'px Sans Serif'
        this.ctx.fillStyle = 'black'
        this.ctx.fillText(sp(l), x, y + fA)
      }
    }
  }